Este trabalho tem como o objetivo fazer uma análise em cima de uma base dados sobre as viagens de táxi em Nova York no ano de 2009 a 2012. Todos os dados usados foram disponibilizados pela Data Sprints e estão sendo armazenados em buckets da Amazon para uma melhor disponibilidade, mas originalmente são derivados de fontes de dados abertas. Bases semelhantes podem ser encontradas no Keggle ou em sites como NYC.gov, um site governamental que disponibiliza algumas DGA’s (Dados Governamentais Abertas).
O intuito dessa análise é mostrar de forma bem simples a manipulação de bases de dados muito grandes, utilizando serviços da amazon para melhor processamento e linguagens como SQL e Python para consultar, manipular e criar visualizações gráficas de forma eficiente e simples.
As tecnologias usadas para a composição desta análise são:
E as bibliotecas utilizadas foram:
Ao decorrer da análise, todas as bibliotecas utilizadas serão explicadas com mais detalhes para que o leitor não sinta-se prejudicado por não entender o porque e como está sendo a implementação de cada parte do código.
O primeiro dataset a ser estudado, são os dados sobre as viagens de táxi em Nova York. É uma coleção com 4 arquivos, possuindo 500MB aproximadamente cada, ou seja, 2GB juntando todos os dados que se equivale a 4 milhões de registros.
Para não ocupar muito armazenamento em disco, e não pesar para fazer tráfego de rede, foi usado a estratégia de se criar um cluster simples na Amazon contendo o serviço de Redshift DatawareHouse para um melhor desempenho em consultas paralelizadas e uma instância EC2 (Elastic Compute Cloud) a qual vai rodar um script em python que irá coletar todos os dados dos 4 datasets e o fará todo o ETL para inseri-los no banco de dados da Redshift.
- O cluster da Amazon RedShift foi criado com as configurações mais simples, é um banco dedos baseado em PostgresSQL que tem um desempenho grande para consultas paralela obtendo assim uma rápida resposta por execução de consulta.É composto por um cluster com 2 nós, sendo que possuí a arquitetura mais simples permitida, instâncias DC2 large com 15.25GiB de RAM e um armazenamento SSD 160GB.
- A instância EC2 utilizada foi uma T2 Large, escolhida por ser dentre as máquinas mais simple que contem a maior quantidade de CPU's e uma maior carga de rede. Está rodando Ubuntu server 18.04 LTS como sistema operacional, possui 8GB de memória em disco, 2 CPU's e 16GB de RAM. A máquina pode ser um pouco a mais do que o necessário, mas será desativada logo após o ETL, que não irá demorar nem 30 minutos.
O script em sql utilizado de construção da tabela Trip que representa as viagens do dataset no banco de dados:
DROP SCHEMA IF EXISTS NYC CASCADE;
CREATE SCHEMA IF NOT EXISTS NYC;
CREATE TABLE IF NOT EXISTS NYC.TRIP(
VENDOR_ID VARCHAR(50) NOT NULL,
PICKUP_DATETIME TIMESTAMP NOT NULL,
DROPOFF_DATETIME TIMESTAMP NOT NULL,
PASSENGER_COUNT SMALLINT NOT NULL,
TRIP_DISTANCE NUMERIC(7,2) NOT NULL,
PICKUP_LONGITUDE NUMERIC(11,7),
PICKUP_LATITUDE NUMERIC(11,7),
DROPOFF_LONGITUDE NUMERIC(11,7),
DROPOFF_LATITUDE NUMERIC(11,7),
RATE_CODE VARCHAR(100) NULL,
STORE_AND_FWD_FLAG VARCHAR(100) NULL,
PAYMENT_TYPE VARCHAR(50) NOT NULL,
FARE_AMOUNT DECIMAL(6,2) NOT NULL DEFAULT 0,
SURCHARGE DECIMAL(6,2) NOT NULL DEFAULT 0,
TIP_AMOUNT DECIMAL(6,2) NOT NULL DEFAULT 0,
TOTAL_AMOUNT DECIMAL(6,2) NOT NULL DEFAULT 0,
TOLLS_AMOUNT DECIMAL(6,2) NOT NULL DEFAULT 0
);
- vendor_id: Uma string que representa o fornecedor da viagem.
- pickup_datetime: O horário em que o passageiro iniciou a viagem.
- dropoff_datetime: O horário em que o passageiro finalizou sua viagem.
- passanger_count: Quantidade de passageiros na viagem.
- trip_distance: A distância arredondada em milhas registrada pelo taxímetro
- pickup_longitude: As cordenadas de longitude de onde o(s) passageiro(s) iniciaram a corrida.
- pickup_latitude: As cordenadas de latitude de onde o(s) passageiro(s) iniciaram a corrida.
- dropoff_longitude: As cordenadas de longitude de onde o(s) passageiro(s) finalizaram a corrida.
- dropoff_latitude: As cordenadas de latitude de onde o(s) passageiro(s) finalizaram a corrida.
- rate_code: Uma taxa que representa a taxa em vigor no final da viagem (quase nunca preenchida no dataset).
- store_and_fwd_flag: Esse campo identifica se os dados da viagem foram armazenados na memória do veículo antes de ser retornada para seu fornecedor por motivos de falta de conexão. Sendo 'Y' para sim e 'N' para não.
- payment_type: A forma de pagamento que foi utilizada na viagem. Pode variar entre 'Credit card' - Cartão de crédito, 'Cash' - dinheiro, 'No charge' - Sem custo, 'Dispute' - disputa, 'Unknow' - desconhecida, 'Voided trip' - Viagem anulada.
- fare_amount: Tarifa calculada pelo taxímetro por metro, tomando como base tempo e distância.
- surcharge: Tarifa extra cobrada em casos como viagens em horários de pico ou viagens em horários noturnos.
- tip_amount: Gorjeta dada ao motorista, preenchida automaticamente apenas em casos de cartão de credito.
- total_amount: Quantidade total paga pelo(s) passageiro(s). Gorjetas em dinheiros não ficam inclusas nesse campo.
- tolls_amount: Quantidade gasta em pedágios.
Apos a criação da tabela de viagens(Trip), foram criado os acessos de usuários para fazer o processamento. (OBS: as senhas reais foram substituidas por XXXXXXXX apenas por procedimentos de segurança.)
CREATE USER populator WITH PASSWORD 'XXXXXXXXX';
CREATE USER analytics WITH PASSWORD 'XXXXXXXXX';
GRANT USAGE ON SCHEMA NYC TO populator;
REVOKE ALL ON nyc.trip from populator CASCADE;
GRANT SELECT, INSERT, DELETE, UPDATE ON NYC.trip TO populator;
GRANT USAGE ON SCHEMA NYC TO analytics;
REVOKE ALL ON nyc.trip from analytics CASCADE;
GRANT SELECT ON NYC.trip TO analytics;
No script acima, foram criados 2 usuários, o populator que tera as permissões necessárias para popular a tabela e o analytics que será o responsável por executar consultas.
A seguir está o código do ETL dos dados do dataset:
# -*- coding: utf-8 -*-
"""
Created on Sat Sep 7 10:57:45 2019
@author: Lauro Oliveira <0lilauro7@gmail.com>
"""
import psycopg2
from psycopg2.extras import execute_values
import json
import requests
from pprint import pprint
from multiprocessing import Process
from datetime import datetime
URLS = [
'https://s3.amazonaws.com/data-sprints-eng-test/data-sample_data-nyctaxi-trips-2009-json_corrigido.json',
'https://s3.amazonaws.com/data-sprints-eng-test/data-sample_data-nyctaxi-trips-2010-json_corrigido.json',
'https://s3.amazonaws.com/data-sprints-eng-test/data-sample_data-nyctaxi-trips-2011-json_corrigido.json',
'https://s3.amazonaws.com/data-sprints-eng-test/data-sample_data-nyctaxi-trips-2012-json_corrigido.json'
]
def timerize(f):
def remake_func(*args, **kwargs):
start_time = datetime.now()
f(*args, **kwargs)
delta = datetime.now() - start_time
pprint("Finish function {}. - time ellipsed: {}".format(f.__name__, delta))
return remake_func
def get_connection():
DB_HOST = 'xxxxxxx.cyy2ldnhnbji.xxxxxxxx.redshift.amazonaws.com'
DB_USERNAME = 'populator'
DB_PORT = '5439'
DB_DATABASE = 'analyze'
DB_PASSWORD = 'XXXXXXXXXXXXXX'
try:
connection = psycopg2.connect(
host = DB_HOST,
user = DB_USERNAME,
password = DB_PASSWORD,
port = int(DB_PORT),
dbname = DB_DATABASE
)
return connection
except Exception as exception_connection:
print(str(exception_connection))
exit("Was impossible to connect on {} database".format(DB_DATABASE))
return None
def insert(values, con):
command = """
INSERT
INTO NYC.TRIP (
DROPOFF_DATETIME,
DROPOFF_LATITUDE,
DROPOFF_LONGITUDE,
FARE_AMOUNT,
PASSENGER_COUNT,
PAYMENT_TYPE,
PICKUP_DATETIME,
PICKUP_LATITUDE,
PICKUP_LONGITUDE,
RATE_CODE,
STORE_AND_FWD_FLAG,
SURCHARGE,
TIP_AMOUNT,
TOLLS_AMOUNT,
TOTAL_AMOUNT,
TRIP_DISTANCE,
VENDOR_ID
) VALUES %s """
try:
execute_values(con.cursor(), command, values)
except:
print(" ======= An erro ocurred !!")
finally:
con.commit()
def process_lines(url):
con = get_connection()
with requests.get(url, stream=True) as request:
request.raise_for_status()
i = 0
counter = 0
insertion_values = []
for chunk in request.iter_lines(decode_unicode=True):
if chunk:
try:
chunk_dict = json.loads(chunk)
insertion_values.append(
(
chunk_dict['dropoff_datetime'],
chunk_dict['dropoff_latitude'],
chunk_dict['dropoff_longitude'],
chunk_dict['fare_amount'],
chunk_dict['passenger_count'],
chunk_dict['payment_type'],
chunk_dict['pickup_datetime'],
chunk_dict['pickup_latitude'],
chunk_dict['pickup_longitude'],
chunk_dict['rate_code'],
chunk_dict['store_and_fwd_flag'],
chunk_dict['surcharge'],
chunk_dict['tip_amount'],
chunk_dict['tolls_amount'],
chunk_dict['total_amount'],
chunk_dict['trip_distance'],
chunk_dict['vendor_id'],
)
)
counter+= 1
i+= 1
except Exception as ex :
print(ex)
pprint(i)
if counter >= 4000:
insert(insertion_values, con)
counter = 0
insertion_values = []
@timerize
def pool_function(urls):
for url in urls:
proc = Process(target=process_lines, args=(url,))
proc.start()
proc.join()
if __name__ == '__main__':
pool_function(URLS)
Os datasets de viagens foram disponibilizados pela Data Sprints, que publicou em 4 links diferentes cada um dos arquivos, sendo referente aos anos de coleta de dados: 2009, 2010, 2011, 2012. Os dados estão estruturados como objetos, sendo cada linha, um dicionario de chave e valor referente a viagem.
Para a coleta desses dados foi usado uma função da biblioteca requests do pyhton que consegue fazem um stream dos arquivos de resposta das requisições enviadas. E com o auxilio da função iter_lines() foi possivel iterar cada registro do stream do arquivo em download para lê-lo como um json e em seguida carrega-lo no banco de dados. Tudo isso pode ser visto na função process_lines().
Como todos os dados, estão dividido em 4 endereços, foi utilizado a biblioteca Multiprocessing que nos disponibiliza uma variedade de funções e classes para executar de forma paralela varios processos. Como pode notar na função pool_function(), foi feito uma iteração para executar todos os 4 endereço de forma assincrona e paralela.
A inserção no banco de dados acontece a cada 4000 registros processados por meio da função execute_values() da biblioteca psycopg2, mas em caso de algum gargalo, é possível optimizar o processo diminuindo a quantidade de inserção por vez, como por exemplo reduzir o valor para 2000 ou em casos criticos 800.
Como vantagem de tudo, ainda temos o fato de que tudo foi executado sem o uso dos 4GB requeridos de memória em disco.
Esse processo foi feito utilzando uma máquina secundária pois não é possíovel fazer uso de bibliotecas como o Multiprocessing em notebooks hosteados em windows, pois eles fazem fork do processo que executa o notebook e o windows restringe isso.
Para o começo das análises, vamos importar algumas bibliotecas que serão muito utilizadas durante todo o estudo.
Neste momento vamos importar a biblioteca Numpy, que é um pacote para computação científica que contém como principal módulo, arrays multidimensionais que pode se trabalhar algebra linear de forma muito simples. Além de ser requerimento de outras bibliotecas como o Pandas.
O Pandas é uma biblioteca de estrutura de dados que serve para alta performance de análise de dados.
Altair e Matplotlib são 2 bibliotecas excelentes para a visualização de dados em gráficos diversificados. Ambas trabalham com estruturas de dados como o Pandas. O Altair tem como peculiaridade o uso do módulo Vega para a renderização de gráficos em notebooks como o utilizado para a apresentação do estudo. E foi escolhida como um adicional para o Matplotlib por conseguir processar gráficos encima de mapas de forma muito simples.
Usaremos o Psycopg2 para estabelecer conexões com a base de dados da Amazon Redshift para fazermos pesquisas usando queries em SQL.
Nesse momento vamos fazer a instalação das bibliotecas que vamos utilizar a seguir em nosso processo.
! pip install psycopg2 numpy pandas scikit-learn scipy matplotlib altair vega_datasets vega IPython boto3
import psycopg2
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import altair as alt
Para começar, vamos fazer uma consulta simples que retorna-rá a distância percorrida em viagens com no máximo 2 passageiros.
Na função a seguir vamos criar a função de conexão com o banco de dados, que ira nos ajudar sempre que precisarmos fazer uma nova consulta. Nela, utilizamos a biblioteca do psycopg que consegue conectar com o Redshift da Amazon de forma bem simples.
def get_connection():
DB_HOST = 'redshift-cluster-nyc.cyy2ldnhnbji.us-east-2.redshift.amazonaws.com'
DB_USERNAME = 'analytics'
DB_PORT = '5439'
DB_DATABASE = 'analyze'
DB_PASSWORD = 'wVRF2iWdqoEHX3EeFLwugh'
try:
connection = psycopg2.connect(
host = DB_HOST,
user = DB_USERNAME,
password = DB_PASSWORD,
port = int(DB_PORT),
dbname = DB_DATABASE
)
return connection
except Exception as exception_connection:
print(str(exception_connection))
exit("Was impossible to connect on {} database".format(DB_DATABASE))
return None
Após criarmos a conexão com o banco, vamos agora focar em como adequar nossos dados. Para isso, utilizaremos a biblioteca do pandas. Por meio de uma de suas funções, a read_sql() vamos conseguir ja trazer a query consultada no banco de dados para uma estrutura pandas.
query = """
SELECT
T.TRIP_DISTANCE as "distance"
FROM NYC.TRIP T
WHERE
T.PASSENGER_COUNT <= 2;
"""
base_dataset = pd.read_sql(query, con=get_connection())
base_dataset.sample(10)
fig, ax = plt.subplots()
fig.figsize=(12, 10)
ax.figure.set_size_inches(12, 10)
ax.set_ylabel("Frequência de ocorrência", fontsize=13)
ax.set_xlabel("Milhas percorridas", fontsize=13)
ax.yaxis.set_tick_params(labelsize=13)
ax.xaxis.set_tick_params(labelsize=13)
plt.hist(base_dataset.distance, 10)
plt.show()
Com o gráfico acima, conseguimos notar que a média está obviamente localizada entre distâncias menores que 10 milhas. Para verificar isso melhor, vamos ampliar a visão dos dados e plotar uma amostra que contenha apenas os registros menores que 5 milhas.
sample = base_dataset.loc[base_dataset.distance <= 5]
sample.sample(3)
edge = .5
bins = np.arange(edge, 5,edge)
y_values = np.array(())
for value in bins:
counter = base_dataset.loc[(base_dataset.distance > value - edge) & (base_dataset.distance <= value)].count()
y_values = np.append(y_values, counter)
y_values
fig, ax = plt.subplots()
fig.figsize=(12, 10)
ax.figure.set_size_inches(12, 10)
ax.set_ylabel("Frequência de ocorrência", fontsize=13)
ax.set_xlabel("Milhas percorridas", fontsize=13)
ax.yaxis.set_tick_params(labelsize=13)
ax.xaxis.set_tick_params(labelsize=13)
ax.bar(bins, y_values, color="C0", align = 'center', width=.4)
plt.show()
Com isso vemos que a distribuição da distancia das viagens com 2 passageiros estão distribuídas em maior em pequenas corridas com menos de 2 milhas. E a média das distancias é:
base_dataset.distance.mean()
Para nossas proximas atividades, vamos usar a seguinte premissa: "Quais os 3 maiores companhias em quantidade total de dinheiro arrecadado"
Para as próximas análises vamos utilizar um dataset extra, ele é um csv pequeno que contem informações sobre todos os fornecedores. Possuí apenas 4 linhas de registro, então vamos importa-ló de forma padrão.
Os dados present no CSV de fornecedores são:
- vendor_id: Representa a chave da companhia de táxi.
- name: Representa o nome completo da companhia.
- address: É o endereço da companhia.
- city: Cidade do endereço da companhia.
- state: O estado em que a companhia se encontra.
- country: País em que a companhia se encontra.
- contact: Email de contato da companhia em questão.
- current: Status da companhia atualmente, sendo 'Yes' para ativa e 'No' para desativada.
vendors = pd.read_csv('./dataset/data-vendor_lookup-csv.csv', sep=",")
vendors
Vamos agora consultar nossa base de dados utilizando uma query que resultara o resultado das 3 maiores companhias em questão de dinheiro arrecadado.
(Obs: Para fazer a consulta, apenas o campo de preço total da corrida está sendo considerado. Não será utilizado nenhuma forma de "limpar" o valor, como retirar a gorjeta ou mesmo ignorar as viagens classifcadas como anuladas.)
query = """
SELECT
SUM(T.TOTAL_AMOUNT) AS "value_accumulated",
T.VENDOR_ID AS "vendor"
FROM NYC.TRIP T
GROUP BY T.VENDOR_ID
ORDER BY SUM(T.TOTAL_AMOUNT) DESC
LIMIT 3;
"""
base_dataset = pd.read_sql(query, con=get_connection())
base_dataset
base_dataset.vendor.count() +1
for idx, register in enumerate(range(1, base_dataset.vendor.count() + 1)):
vendor = base_dataset.loc[idx, 'vendor']
print("{}º lugar, com US$ {} - {}".format(
idx + 1,
base_dataset.loc[idx, 'value_accumulated'],
vendors.loc[vendors.vendor_id == vendor].name.values[0]
))
A seguir vamos então fazer uma análise do histograma de todos os meses por ano das corridas pagas em dinheiro.
O campo payment_type no banco de dados não está recebendo os valores adequados, existem grandes variações dos valores existentes lá. E como queremos fazer uma consulta por método de pagamento em dinheiro(Cash), é um pouco difícil ajustar a query para trazer todos esse valores.
Para essa ocasião também existe um dataset pequeno em CSV que possuí todas as variações das palavras referentes aos métodos de pagamento, incluiindo o "Cash"
Vamos pegar esse dataset e montar uma string contendo as variações da palavra "Cash" no banco dedos que será usada na consulta.
payments = pd.read_csv('./dataset/data-payment_lookup-csv.csv', sep=',')
payments_cash = payments.loc[payments.B =='Cash'].A.values.tolist()
mapped = map(
lambda x: "'{}'".format(x),
payments_cash
)
cash_words = " ,".join(list(mapped))
cash_words
Agora vamos usar os valores que geramos para consultar na base de dados.
query = """
SELECT
COUNT(T.VENDOR_ID) AS "occurency",
CONCAT(
CONCAT(
CAST(DATE_PART('YEAR', T.pickup_datetime) AS VARCHAR(4)),
'/'::VARCHAR(1)
),
CAST(DATE_PART('MONTH', T.pickup_datetime) AS VARCHAR(2))
) AS "key",
CAST(
DATE_PART('YEAR', T.PICKUP_DATETIME) AS VARCHAR(4)
) AS "year",
CAST(
DATE_PART('MONTH', T.PICKUP_DATETIME) AS VARCHAR(2)
) AS "month"
FROM NYC.TRIP T
WHERE T.payment_type IN ({})
GROUP BY 3, 4;
""".format(cash_words)
base_dataset = pd.read_sql(query, con=get_connection())
base_dataset.sample(3)
base_dataset.month = base_dataset.month.apply(pd.to_numeric, errors='ignore')
base_dataset.year = base_dataset.year.apply(pd.to_numeric, errors='ignore')
base_dataset = base_dataset.sort_values(['year', 'month'], ascending=[True, True])
base_dataset.sample(3)
Como é possivel perceber, está faltando 4 meses no histórico. Poderiamos adicionar as linhas faltantes com o valor de 0 ou com a média do mês anterior e o sucessor, mas por hora vamos ignorar esse problema.
fig, ax = plt.subplots()
fig.figsize=(20, 10)
ax.figure.set_size_inches(20, 10)
ax.set_ylabel("Corridas acontecidas", fontsize=14)
ax.set_xlabel("Ano/mês", fontsize=14)
ax.yaxis.set_tick_params(labelsize=12)
ax.xaxis.set_tick_params(labelsize=13)
ax.bar(base_dataset.key, base_dataset.occurency, color="C0", align = 'center', width=.4)
ax.set_xticklabels(base_dataset.key, rotation=90, fontsize=13)
plt.show()
A partir dessa visualização se torna notável que nos finais de ano, a quantidade de corridas em dinheiro sempre acontece uma queda, e que também, o mesmo padrão de ocorrências de corridas se mantem o mesmo para os repetidos anos.
Agora que já vimos o total de custo das corridas de táxi por mês, vamos focar especificamente nos 3 ultimos meses do ultimo ano de registro. E vamos veficar o quanto de gorjeta as corridas de táxis renderam para cada dia.
Vamos construir uma query que fará toda a seleção dos valores, em seguida vamos mostrar em uma série temporal esses valores.
query = """
SELECT
SUM(T.TIP_AMOUNT) AS "tip",
CONCAT(
CONCAT(
CAST(DATE_PART('MONTH', T.PICKUP_DATETIME) AS VARCHAR(2)),
'/'
),
CAST(DATE_PART('DAY', T.PICKUP_DATETIME) AS VARCHAR(2))
) AS "key",
CAST(
DATE_PART('MONTH', T.PICKUP_DATETIME) AS VARCHAR(2)
) AS "month",
CAST(
DATE_PART('DAY', T.PICKUP_DATETIME) AS VARCHAR(2)
) AS "day"
FROM NYC.TRIP T
WHERE
DATE_PART('YEAR', T.PICKUP_DATETIME) = 2012
AND
DATE_PART('MONTH', T.PICKUP_DATETIME) IN (
SELECT
DISTINCT CAST(DATE_PART('MONTH', T.PICKUP_DATETIME) AS INTEGER) AS "MONTHS"
FROM NYC.TRIP T
WHERE
DATE_PART('YEAR', T.PICKUP_DATETIME) = 2012
ORDER BY 1 DESC
LIMIT 3
)
GROUP BY 3, 4;
"""
base_dataset = pd.read_sql(query, con=get_connection())
base_dataset.month = base_dataset.month.apply(pd.to_numeric, errors='ignore')
base_dataset.day = base_dataset.day.apply(pd.to_numeric, errors='ignore')
base_dataset = base_dataset.sort_values(['month', 'day'], ascending=[True, True])
base_dataset.sample(3)
Agora com o auxílio do Matlibplot vamos montar mais uma vez, a visualização dos dados usando um grafico de barras, para ver a a série temporal das grojetas distribuídas ao longo dos dias.
fig, ax = plt.subplots()
fig.figsize=(24, 10)
ax.figure.set_size_inches(24, 10)
ax.set_ylabel("Gorjeta", fontsize=16)
ax.set_xlabel("Dia", fontsize=16)
ax.yaxis.set_tick_params(labelsize=14)
ax.xaxis.set_tick_params(labelsize=14)
w = .22
ax.bar(
base_dataset.loc[base_dataset.month == 8].day.values - w,
base_dataset.loc[base_dataset.month == 8].tip,
color="#00f429",
align = 'center',
width=w
)
ax.bar(
base_dataset.loc[base_dataset.month == 9].day.values,
base_dataset.loc[base_dataset.month == 9].tip,
color="b",
align = 'center',
width=w
)
ax.bar(
base_dataset.loc[base_dataset.month == 10].day.values + w,
base_dataset.loc[base_dataset.month == 10].tip,
color="#ff0050",
align = 'center',
width=w
)
ax.legend(['Agosto','Setembro', 'Outubro'], loc='upper right', fontsize=12)
ax.xaxis.set_ticks(range(1, 33))
plt.show()
É possível ver acima que os registros estão muito equilibrados e que diariamente há mais que mil dólares gastos em gorjeta em táxis de Nova York.
A partir de agora vamos ver nossos dados a partir de outro ponto muito importante. Vamos avaliar o tempo de corridas de táxis. Para isso, vamos limitar nossa população para uma pequena amostra, apenas os dias de final de semana (sábado e domingo).
Vamos realizar um consulta em nossa base que já vai trazer para nós o tempo de viagem em minutos, o horário de início e o horário de fim.
query = """
SELECT
DATEDIFF('MINUTE', T.PICKUP_DATETIME::TIMESTAMP, T.DROPOFF_DATETIME::TIMESTAMP) AS "time_trip",
T.PICKUP_DATETIME AS "pick_up",
T.DROPOFF_DATETIME AS "drop_off"
FROM NYC.TRIP T
WHERE
EXTRACT(DOW FROM T.PICKUP_DATETIME::TIMESTAMP) IN (0, 6);
"""
base_dataset = pd.read_sql(query, con=get_connection())
base_dataset.sample(3)
Selecionaremos os valores únicos para ver o quão complexo é fazer a distribuição desses valores em um histograma.
fig, ax = plt.subplots()
fig.figsize=(15, 10)
ax.figure.set_size_inches(15, 10)
ax.set_ylabel("Frequência de ocorrência", fontsize=13)
ax.set_xlabel("Tempo de viagem", fontsize=13)
ax.yaxis.set_tick_params(labelsize=13)
ax.xaxis.set_tick_params(labelsize=13)
plt.hist(base_dataset.time_trip, 50)
plt.show()
Com isso é poossível notar que as viagens que levam pouco tempo, são as mais comuns de acontecerem no final de semana. Vamos comprovar isso a partir da média:
round(base_dataset.time_trip.mean(), 2)
Agora que já entendemos um ponco dos dados presentes em nossa base, vamos tentar plota-lós em um mapa. Pela quantidade gigantesca de registro, vamos reduzir nossas métricas, iremos utilizar apenas os dados referente ao ano de 2010 para exibir no maapa os pontos em que passageiros foram deixados.
Até o momento estivemos usando a biblioteca do matplotlib para exibir gráficos, mas vamos trocar para uma chamda Altair, que nos ajudará imensamente na renderização de dados em mapas com poucas linhas de código.
query = """
SELECT
T.DROPOFF_LONGITUDE AS "longitude",
T.DROPOFF_LATITUDE AS "latitude"
FROM NYC.TRIP T
WHERE
DATE_PART('YEAR', T.PICKUP_DATETIME) = 2010;
"""
dropoff_dataset = pd.read_sql(query, con=get_connection())
dropoff_dataset.describe()
É possivel notar a partir da função de describe, que existem alguns outliers, pois estão com os valores bem próximos de 0. Isso pode ser um problema quando a visualização for gerada. Não só valores
(Vamos usar o valor -73.7 a -74.3 para limitar a longitude e 40.1 a 40.9 para a latitude, pois geograficamente, representam pontos que delimitam a área de Nova York)
outlier_counter = dropoff_dataset.loc[
(dropoff_dataset.longitude > -73.7) | (dropoff_dataset.latitude < 40.1 ) |
(dropoff_dataset.longitude < -74.3) | (dropoff_dataset.latitude > 40.9 )
].longitude.count()
print("Quantidade de Outliers: {}".format(outlier_counter))
outlier_percentage = (outlier_counter / dropoff_dataset.latitude.count()) * 100
print("Porcentagem de Outilers no dataset: {}%".format(round(outlier_percentage, 2)))
Como a quantidade de outliers é bem baixa, poderiamos fazer uma média das longitudes e latitudes e substituir nesses valores ou exclui-lós. Neste momento, vamos optar por exclui-lós pois existem muitos valores, e se substituíssemos todos pela mesma média, aconteceria de ficar uma grande marca no mapa.
dropoff_treated = dropoff_dataset.loc[
(dropoff_dataset.longitude <= -73.7) & (dropoff_dataset.longitude >= -74.3 ) &
(dropoff_dataset.latitude >= 40.1 ) & (dropoff_dataset.latitude <= 40.9)
]
dropoff_treated.describe()
alt.data_transformers.disable_max_rows()
alt.renderers.enable('notebook')
# Vamos usar o Topojson da Cidade de New York, disponibilizados no Github.
# https://raw.githubusercontent.com/deldersveld/topojson/master/countries/us-states/NJ-34-new-jersey-counties.json
# https://raw.githubusercontent.com/codeforamerica/click_that_hood/master/public/data/new-york-city-boroughs.geojson
ny = alt.topo_feature('https://raw.githubusercontent.com/codeforamerica/click_that_hood/master/public/data/new-york-city-boroughs.geojson', 'feature')
background_two = alt.Chart(ny).mark_geoshape(
fill='lightgray',
strokeWidth=1,
stroke='black',
strokeOpacity=0
).properties(
width=900,
height=900
)
points = alt.Chart(dropoff_treated).mark_circle(
size=7,
opacity=0.05,
stroke='transparent',
color='red',
strokeOpacity=1,
).encode(
longitude='longitude:Q',
latitude='latitude:Q'
)
(background_two + points).configure_view(stroke=None)